﻿using UnityEngine;
using System.Collections.Generic;
using System;

namespace ZiZi.Graphics.OfflineBaker
{
    ///////////////////////////////////////// Baking Assemble Parameters /////////////////////////////////////////
    [Serializable]
    public class BakeObjectAssembleParams
    {
        public bool autoAddCollider = true;
        public bool addChildMesh = true;
    }

    ///////////////////////////////////////// container for baking object /////////////////////////////////////////
    [Serializable]
    public class BakeObject
    {
        private GameObject _obj;
        private Mesh _mesh;
        private Collider _meshCollider;
        private bool _hasColliderBeforeAdd;
        private bool _available;
        private bool _isEnable = true;


        public BakeObject()
        {
            _obj = null;
            _mesh = null;
            _meshCollider = null;
            _hasColliderBeforeAdd = false;
            _available = false;
            _isEnable = true;
        }

        /// <summary>
        /// Add a Gameobject to bakeObject
        /// </summary>
        /// <param name="_obj"></param>
        /// <returns></returns>
        public bool AddObject(GameObject _obj)
        {
            this._obj = _obj;
            
            if (!TryFetchSharedMesh(_obj, out this._mesh))
                return false;

            Collider mc = _obj.GetComponent<Collider>();

            if (mc == null)
            {
                _hasColliderBeforeAdd = false;
                MeshCollider mcc = _obj.AddComponent<MeshCollider>();
                mcc.sharedMesh = _mesh;
                _meshCollider = mcc;
            }

            else
            {
                _hasColliderBeforeAdd = true;
                _meshCollider = mc;
            }

            
            //BakeObejctHelper helper = _obj.AddComponent<BakeObejctHelper>();
            //helper.bakeObject = this;

            return CheckAvailable();
        }

        /// <summary>
        /// Add a mesh to bakeObject.
        /// </summary>
        /// <param name="_mesh"></param>
        /// <param name="the tranform of mesh"></param>
        /// <returns></returns>
        public bool AddMesh(Mesh _mesh, Transform tranform)
        {
            GameObject dummy = new GameObject("dummy");
            dummy.transform.parent = tranform;
            dummy.transform.localRotation = Quaternion.identity;
            dummy.transform.localScale = Vector3.one;
            dummy.transform.localPosition = Vector3.zero;
            dummy.transform.parent = null;
            dummy.AddComponent<MeshFilter>().sharedMesh = _mesh;
            dummy.AddComponent<MeshCollider>().sharedMesh = _mesh;
            return AddObject(dummy);
        }

        /// <summary>
        /// Release the Object added by bakeObject.
        /// </summary>
        public void ReleaseObject()
        {
            if (_obj != null && !_hasColliderBeforeAdd)
            {
                GameObject.Destroy(_obj.GetComponent<Collider>());
            }

            _obj = null;
            _mesh = null;
            _meshCollider = null;
            _available = false;
            _hasColliderBeforeAdd = false;
        }


        /// <summary>
        /// Try fetch sharedMesh from a gameObject.
        /// </summary>
        /// <param name="obj"></param>
        /// <param name="out_Mesh"></param>
        /// <returns></returns>
        static public bool TryFetchSharedMesh(GameObject obj, out Mesh out_Mesh)
        {
            MeshFilter mf = obj.GetComponent<MeshFilter>();
            if (mf != null)
            {
                if (mf.sharedMesh != null)
                {
                    out_Mesh = mf.sharedMesh;
                    return true;
                }
            }
            SkinnedMeshRenderer smf = obj.GetComponent<SkinnedMeshRenderer>();
            if (smf != null)
            {
                if (smf.sharedMesh != null)
                {
                    out_Mesh = smf.sharedMesh;
                    return true;
                }
            }

            out_Mesh = null;
            return false;
        }

        public static List<BakeObject> CreateBakeObjectRecursive(GameObject obj, BakeObjectAssembleParams param = null)
        {
            List<BakeObject> result = new List<BakeObject>();
            if (param == null)
                param = new BakeObjectAssembleParams();

            if (obj == null)
                return result;

            BakeObject bakeObj = new BakeObject();
            if (bakeObj.AddObject(obj))
                result.Add(bakeObj);

            if (param.addChildMesh)
            {
                Transform parentTrans = obj.transform;
                int childCount = parentTrans.childCount;
                for (int c = 0; c < childCount; ++c)
                {
                    GameObject child = parentTrans.GetChild(c).gameObject;
                    result.AddRange(CreateBakeObjectRecursive(child, param));
                }
            }

            return result;
        }

        // object to bake
        public GameObject Object
        {
            get { return _obj; }
        }

        // mesh
        public Mesh Mesh
        {
            get { return _mesh; }
        }
        // collider
        public Collider MeshColider
        {
            get { return _meshCollider; }
        }

        // is ready to bake.
        public bool Available
        {
            get { return _available; }
        }
        public bool Enable
        {
            get { return _isEnable; }
            set { _isEnable = value; }
        }

        private bool CheckAvailable()
        {
            _available = (_obj != null) && (_mesh != null) && (_meshCollider != null);
            return _available;
        }
    };

    ///////////////////////////////////////// Baking Assembler /////////////////////////////////////////
    public class BakingTask
    {
        private List<BakeObject> _objectToBake;
        private List<BakeObject> _extraObject;

        public List<BakeObject> ObjectToBake
        {
            get { return _objectToBake; }
        }

        public List<BakeObject> ExtraObject
        {
            get { return _extraObject; }
        }

        public BakingTask()
        {
            _objectToBake = new List<BakeObject>();
            _extraObject = new List<BakeObject>();
        }

        /// <summary>
        /// Clear All append Objects.
        /// </summary>
        public void RemoveAllExtraObjects()
        {
            _RemoveAllContainers(ref _extraObject);
        }

        /// <summary>
        /// Clear All baking objects.
        /// </summary>
        public void RemoveAllBakingObject()
        {
            _RemoveAllContainers(ref _objectToBake);
        }

        private void _RemoveAllContainers(ref List<BakeObject> _container)
        {
            int count = _container.Count;

            for (int i = 0; i < count; ++i)
            {
                _container[i].ReleaseObject();
            }
            _container.Clear();
        }

        public bool AddBakeObject(BakeObject obj)
        {
            if (obj.Available)
            {
                _objectToBake.Add(obj);
                return true;
            }

            return false;
        }

        public bool AddExtraObject(BakeObject obj)
        {
            if (obj.Available)
            {
                _extraObject.Add(obj);
                return true;
            }

            return false;
        }

        public void AddBakeObject(GameObject obj, BakeObjectAssembleParams param = null)
        {
            _objectToBake.AddRange(BakeObject.CreateBakeObjectRecursive(obj, param));
        }

        public void AddExtraObject(GameObject obj, BakeObjectAssembleParams param = null)
        {
            _extraObject.AddRange(BakeObject.CreateBakeObjectRecursive(obj, param));
        }
    }
}